# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Installs and configures Ceilometer
"""

import uuid

from packstack.installer import basedefs
from packstack.installer import utils
from packstack.installer import validators
from packstack.installer import processors
from packstack.installer.utils import split_hosts

from packstack.modules.documentation import update_params_usage
from packstack.modules.shortcuts import get_mq
from packstack.modules.ospluginutils import appendManifestFile
from packstack.modules.ospluginutils import createFirewallResources
from packstack.modules.ospluginutils import getManifestTemplate
from packstack.modules.ospluginutils import generate_ssl_cert

# ------------- Ceilometer Packstack Plugin Initialization --------------

PLUGIN_NAME = "OS-Ceilometer"
PLUGIN_NAME_COLORED = utils.color_text(PLUGIN_NAME, 'blue')


def initConfig(controller):
    ceilometer_params = {
        "CEILOMETER": [
            {"CONF_NAME": "CONFIG_CEILOMETER_SECRET",
             "CMD_OPTION": "ceilometer-secret",
             "PROMPT": "Enter the Ceilometer secret key",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_not_empty],
             "DEFAULT_VALUE": uuid.uuid4().hex[:16],
             "MASK_INPUT": True,
             "LOOSE_VALIDATION": False,
             "USE_DEFAULT": True,
             "NEED_CONFIRM": True,
             "CONDITION": False},

            {"CONF_NAME": "CONFIG_CEILOMETER_KS_PW",
             "CMD_OPTION": "ceilometer-ks-passwd",
             "PROMPT": "Enter the password for the Ceilometer Keystone access",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_not_empty],
             "DEFAULT_VALUE": "PW_PLACEHOLDER",
             "PROCESSORS": [processors.process_password],
             "MASK_INPUT": True,
             "LOOSE_VALIDATION": False,
             "USE_DEFAULT": False,
             "NEED_CONFIRM": True,
             "CONDITION": False},

            {"CONF_NAME": "CONFIG_CEILOMETER_COORDINATION_BACKEND",
             "CMD_OPTION": "ceilometer-coordination-backend",
             "PROMPT": "Enter the coordination driver",
             "OPTION_LIST": ['redis', 'none'],
             "VALIDATORS": [validators.validate_options],
             "DEFAULT_VALUE": 'redis',
             "MASK_INPUT": False,
             "USE_DEFAULT": True,
             "NEED_CONFIRM": False,
             "CONDITION": False},
        ],

        "MONGODB": [
            {"CMD_OPTION": "mongodb-host",
             "PROMPT": "Enter the host for the MongoDB server",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_ssh],
             "DEFAULT_VALUE": utils.get_localhost_ip(),
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": True,
             "CONF_NAME": "CONFIG_MONGODB_HOST",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
        ],
        "REDIS": [
            {"CMD_OPTION": "redis-master-host",
             "PROMPT": "Enter the host for the Redis master server",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_ssh],
             "DEFAULT_VALUE": utils.get_localhost_ip(),
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_MASTER_HOST",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False,
             "DEPRECATES": ["CONFIG_REDIS_HOST"]},
            {"CMD_OPTION": "redis-port",
             "PROMPT": "Enter the port of the redis server(s)",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_port],
             "DEFAULT_VALUE": 6379,
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_PORT",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-ha",
             "PROMPT": "Should redis try to use HA?",
             "OPTION_LIST": ["y", "n"],
             "VALIDATORS": [validators.validate_options],
             "DEFAULT_VALUE": "n",
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_HA",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-slaves",
             "PROMPT": "Enter the host for the redis slave servers",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_multi_ssh],
             "DEFAULT_VALUE": "",
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_SLAVE_HOSTS",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-sentinels",
             "PROMPT": "Enter the host for the redis sentinel servers",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_multi_ssh],
             "DEFAULT_VALUE": "",
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_SENTINEL_HOSTS",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-sentinel-contact",
             "PROMPT":
                 "Enter the IP address of the coordination redis sentinel",
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_ssh],
             "DEFAULT_VALUE": "",
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_SENTINEL_CONTACT_HOST",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-sentinel-port",
             "PROMPT": ("Enter the port on which the redis sentinel servers"
                        " listen"),
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_port],
             "DEFAULT_VALUE": 26379,
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_SENTINEL_PORT",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-sentinel-quorum",
             "PROMPT": (
                 "Enter the quorum value for the redis sentinel servers"),
             "OPTION_LIST": [],
             "VALIDATORS": [validators.validate_integer],
             "DEFAULT_VALUE": 2,
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_SENTINEL_QUORUM",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
            {"CMD_OPTION": "redis-sentinel-master-name",
             "PROMPT": (
                 "Enter the logical name of the master server"),
             "OPTION_LIST": [r'[a-z]+'],
             "VALIDATORS": [validators.validate_regexp],
             "DEFAULT_VALUE": 'mymaster',
             "MASK_INPUT": False,
             "LOOSE_VALIDATION": False,
             "CONF_NAME": "CONFIG_REDIS_MASTER_NAME",
             "USE_DEFAULT": False,
             "NEED_CONFIRM": False,
             "CONDITION": False},
        ],
    }
    update_params_usage(basedefs.PACKSTACK_DOC, ceilometer_params)

    ceilometer_groups = [
        {"GROUP_NAME": "CEILOMETER",
         "DESCRIPTION": "Ceilometer Config parameters",
         "PRE_CONDITION": "CONFIG_CEILOMETER_INSTALL",
         "PRE_CONDITION_MATCH": "y",
         "POST_CONDITION": False,
         "POST_CONDITION_MATCH": True},

        {"GROUP_NAME": "MONGODB",
         "DESCRIPTION": "MONGODB Config parameters",
         "PRE_CONDITION": "CONFIG_CEILOMETER_INSTALL",
         "PRE_CONDITION_MATCH": "y",
         "POST_CONDITION": False,
         "POST_CONDITION_MATCH": True},

        {"GROUP_NAME": "REDIS",
         "DESCRIPTION": "Redis Config parameters",
         "PRE_CONDITION": "CONFIG_CEILOMETER_COORDINATION_BACKEND",
         "PRE_CONDITION_MATCH": "redis",
         "POST_CONDITION": False,
         "POST_CONDITION_MATCH": True},
    ]
    for group in ceilometer_groups:
        paramList = ceilometer_params[group["GROUP_NAME"]]
        controller.addGroup(group, paramList)


def initSequences(controller):
    if controller.CONF['CONFIG_CEILOMETER_INSTALL'] != 'y':
        return

    steps = [{'title': 'Adding MongoDB manifest entries',
              'functions': [create_mongodb_manifest]},
             {'title': 'Adding Redis manifest entries',
              'functions': [create_redis_manifest]},
             {'title': 'Adding Ceilometer manifest entries',
              'functions': [create_manifest]},
             {'title': 'Adding Ceilometer Keystone manifest entries',
              'functions': [create_keystone_manifest]}]
    controller.addSequence("Installing OpenStack Ceilometer", [], [],
                           steps)


# -------------------------- step functions --------------------------

def create_manifest(config, messages):
    manifestfile = "%s_ceilometer.pp" % config['CONFIG_CONTROLLER_HOST']
    manifestdata = getManifestTemplate(get_mq(config, "ceilometer"))
    manifestdata += getManifestTemplate("ceilometer")

    if config['CONFIG_CEILOMETER_COORDINATION_BACKEND'] == 'redis':
        # Determine if we need to configure multiple sentinel hosts as
        # fallbacks for use in coordination url.
        sentinel_hosts = split_hosts(config['CONFIG_REDIS_SENTINEL_HOSTS'])
        sentinel_port = config['CONFIG_REDIS_SENTINEL_PORT']
        sentinel_host = config['CONFIG_REDIS_SENTINEL_CONTACT_HOST']
        if config['CONFIG_IP_VERSION'] == 'ipv6':
            config['CONFIG_REDIS_SENTINEL_CONTACT_HOST_URL'] = "[%s]" % (
                sentinel_host)
        else:
            config['CONFIG_REDIS_SENTINEL_CONTACT_HOST_URL'] = sentinel_host

        sentinel_contact = config['CONFIG_REDIS_SENTINEL_CONTACT_HOST']
        if len(sentinel_hosts) > 1:
            sentinel_format = 'sentinel_fallback=%s:%s'
            if config['CONFIG_IP_VERSION'] == 'ipv6':
                sentinel_format = 'sentinel_fallback=[%s]:%s'

            sentinel_fallbacks = '&'.join([sentinel_format %
                                          (host, sentinel_port)
                                          for host in sentinel_hosts
                                          if host != sentinel_contact])
        else:
            sentinel_fallbacks = ''
        config['CONFIG_REDIS_SENTINEL_FALLBACKS'] = sentinel_fallbacks

    if config['CONFIG_AMQP_ENABLE_SSL'] == 'y':
        ssl_cert_file = config['CONFIG_CEILOMETER_SSL_CERT'] = (
            '/etc/pki/tls/certs/ssl_amqp_ceilometer.crt'
        )
        ssl_key_file = config['CONFIG_CEILOMETER_SSL_KEY'] = (
            '/etc/pki/tls/private/ssl_amqp_ceilometer.key'
        )
        ssl_host = config['CONFIG_CONTROLLER_HOST']
        service = 'ceilometer'
        generate_ssl_cert(config, ssl_host, service, ssl_key_file,
                          ssl_cert_file)

    fw_details = dict()
    key = "ceilometer_api"
    fw_details.setdefault(key, {})
    fw_details[key]['host'] = "ALL"
    fw_details[key]['service_name'] = "ceilometer-api"
    fw_details[key]['chain'] = "INPUT"
    fw_details[key]['ports'] = ['8777']
    fw_details[key]['proto'] = "tcp"
    config['FIREWALL_CEILOMETER_RULES'] = fw_details
    manifestdata += createFirewallResources('FIREWALL_CEILOMETER_RULES')

    # Add a template that creates a group for nova because the ceilometer
    # class needs it
    if config['CONFIG_NOVA_INSTALL'] == 'n':
        manifestdata += getManifestTemplate("ceilometer_nova_disabled")
    appendManifestFile(manifestfile, manifestdata, 'ceilometer')


def create_mongodb_manifest(config, messages):
    host = config['CONFIG_MONGODB_HOST']
    if config['CONFIG_IP_VERSION'] == 'ipv6':
        config['CONFIG_MONGODB_HOST_URL'] = "[%s]" % host
    else:
        config['CONFIG_MONGODB_HOST_URL'] = host
    manifestfile = "%s_mongodb.pp" % config['CONFIG_MONGODB_HOST']
    manifestdata = getManifestTemplate("mongodb")

    fw_details = dict()
    key = "mongodb_server"
    fw_details.setdefault(key, {})
    fw_details[key]['host'] = "%s" % config['CONFIG_CONTROLLER_HOST']
    fw_details[key]['service_name'] = "mongodb-server"
    fw_details[key]['chain'] = "INPUT"
    fw_details[key]['ports'] = ['27017']
    fw_details[key]['proto'] = "tcp"
    config['FIREWALL_MONGODB_RULES'] = fw_details

    manifestdata += createFirewallResources('FIREWALL_MONGODB_RULES')
    appendManifestFile(manifestfile, manifestdata, 'pre')


def create_redis_manifest(config, messages):
    if config['CONFIG_CEILOMETER_COORDINATION_BACKEND'] == 'redis':
        redis_master_host = config['CONFIG_REDIS_MASTER_HOST']
        if config['CONFIG_IP_VERSION'] == 'ipv6':
            config['CONFIG_REDIS_MASTER_HOST_URL'] = "[%s]" % redis_master_host
        else:
            config['CONFIG_REDIS_MASTER_HOST_URL'] = redis_master_host

        # master
        manifestfile = "%s_redis.pp" % config['CONFIG_REDIS_MASTER_HOST']
        manifestdata = getManifestTemplate("redis.pp")

        master_clients = set([config['CONFIG_CONTROLLER_HOST']]).union(
            split_hosts(config['CONFIG_REDIS_SLAVE_HOSTS'])).union(
            split_hosts(config['CONFIG_REDIS_SENTINEL_HOSTS']))
        config['FIREWALL_REDIS_RULES'] = _create_redis_firewall_rules(
            master_clients, config['CONFIG_REDIS_PORT'])

        manifestdata += createFirewallResources('FIREWALL_REDIS_RULES')
        appendManifestFile(manifestfile, manifestdata, 'pre')

        # slaves
        if config['CONFIG_REDIS_HA'] == 'y':
            for slave in split_hosts(config['CONFIG_REDIS_SLAVE_HOSTS']):
                config['CONFIG_REDIS_HOST'] = slave
                manifestfile = "%s_redis_slave.pp" % slave
                manifestdata = getManifestTemplate("redis_slave.pp")

                slave_clients = set([config['CONFIG_CONTROLLER_HOST']]).union(
                    split_hosts(config['CONFIG_REDIS_SLAVE_HOSTS'])).union(
                        split_hosts(config['CONFIG_REDIS_SENTINEL_HOSTS']))
                config['FIREWALL_REDIS_SLAVE_RULES'] = (
                    _create_redis_firewall_rules(
                        slave_clients, config['CONFIG_REDIS_PORT']))

                manifestdata += createFirewallResources(
                    'FIREWALL_REDIS_SLAVE_RULES')
                appendManifestFile(manifestfile, manifestdata, 'pre')

        # sentinels
        if config['CONFIG_REDIS_HA'] == 'y':
            for sentinel in split_hosts(config['CONFIG_REDIS_SENTINEL_HOSTS']):
                manifestfile = "%s_redis_sentinel.pp" % sentinel
                manifestdata = getManifestTemplate("redis_sentinel.pp")

                config['FIREWALL_SENTINEL_RULES'] = (
                    _create_redis_firewall_rules(
                        split_hosts(config['CONFIG_REDIS_SENTINEL_HOSTS']),
                        config['CONFIG_REDIS_SENTINEL_PORT']))

                manifestdata += createFirewallResources(
                    'FIREWALL_SENTINEL_RULES')
                appendManifestFile(manifestfile, manifestdata, 'pre')


def create_keystone_manifest(config, messages):
    manifestfile = "%s_keystone.pp" % config['CONFIG_CONTROLLER_HOST']
    manifestdata = getManifestTemplate("keystone_ceilometer")
    appendManifestFile(manifestfile, manifestdata)


# ------------------------- helper functions -------------------------

def _create_redis_firewall_rules(hosts, port):
    fw_details = dict()
    for host in hosts:
        key = "redis service from %s" % host
        fw_details.setdefault(key, {})
        fw_details[key]['host'] = "%s" % host
        fw_details[key]['service_name'] = "redis service"
        fw_details[key]['chain'] = "INPUT"
        fw_details[key]['ports'] = port
        fw_details[key]['proto'] = "tcp"
    return fw_details
